package com.flyaudio.xiaopengautopilot.custom;

import com.flyaudio.xiaopengautopilot.R;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.view.animation.DecelerateInterpolator;

public class FlySeekBar extends View {

	private static final int HORIZONTAL = 0;
	private static final int VERTICAL = 1;
	private static final int THUMB_ANIM_CANCEL_SPACE = 1000;
	private static final int DEFAULT_PROGRESS_BACKGROUND_COLOR = 0xFFDDDDDD;
	private static final int DEFAULT_PROGRESS_COLOR = 0xFF28AAE5;
	private static final int DEFAULT_SECTION_POINT_RADIUS = 6;
	private static final boolean DEFAULT_OVERSPREAD_ENABLE = false;
	private static final boolean DEFAULT_THUMB_SCALE_ENABLE = false;
	private static final int DEFAULT_THUMB_RADIUS = 18;
	private static final int DEFAULT_MIN_PROGRESS = 0;
	private static final int DEFAULT_MAX_PROGRESS = 100;
	private static final int DEFAULT_PROGRESS_CORNER = 2;
	private static final int DEFAULT_PROGRESS_WIDTH = 2;
	private int mOrientation = HORIZONTAL;
	private int mProgressBackgroundColor = DEFAULT_PROGRESS_BACKGROUND_COLOR;
	private int mProgressColor = DEFAULT_PROGRESS_COLOR;
	private int mSectionPointRadius = DEFAULT_SECTION_POINT_RADIUS;
	private int mThumbRadius = DEFAULT_THUMB_RADIUS;
	private int mMinProgress = DEFAULT_MIN_PROGRESS;
	private int mMaxProgress = DEFAULT_MAX_PROGRESS;
	private int mProgressCorner = DEFAULT_PROGRESS_CORNER;
	private int mProgressWidth = DEFAULT_PROGRESS_WIDTH;
	private boolean mOverspreadEnable = DEFAULT_OVERSPREAD_ENABLE;

	private int mProgressLength;
	private int mThumbTouchOffset;
	private int mCurrProgress;
	private int mLastProgress;

	private int mViewHeight;
	private int mViewWidth;
	private int mMaxAnimatorTime = 700;
	private Bitmap mBackgroundBitmap = null;
	private Paint mBackgroundPaint;
	private Rect mBackgroundRect;

	private Paint mProgressBackgroundPaint;
	private Paint mSectionPointPaint;
	private Paint mThumbPaint;
	private Rect mProgressRect;
	private Rect mSectionPointRect;
	private Rect mThumbDestRect;
	private Drawable mThumbDrawable = null;
	private Drawable mBackgroundDrawable = null;
	private Drawable mProgressBackgroundDrawable = null;

	private boolean mIsClickOnThumb = false;
	private boolean mIsClickOnProgress = false;
	private boolean mIsMovedOut = false;
	private boolean mIsFromUser = false;
	private boolean mIsThumbEnable = true;
	private OnProgressChangedListener mOnProgressListener;

	private boolean mThumbScaleEnable = DEFAULT_THUMB_SCALE_ENABLE;
	private ValueAnimator mValueAnimator;
	private float mMaxThumbScale = 1.4f;
	private float mThumbScale = 1.0f;

	private ValueAnimator mToDestValueAnimator;

	public FlySeekBar(Context context) {
		this(context, null);
	}

	public FlySeekBar(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public FlySeekBar(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context, attrs);
	}

	private void init(Context context, AttributeSet attrs) {
		if (null != attrs) {
			TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FlySeekbarObj);

			mBackgroundDrawable = typedArray
					.getDrawable(R.styleable.FlySeekbarObj_mBackground);
			mOrientation = typedArray.getInt(
					R.styleable.FlySeekbarObj_mOrientation, HORIZONTAL);
			mProgressBackgroundDrawable = typedArray
					.getDrawable(R.styleable.FlySeekbarObj_mProgress_background);
			mThumbDrawable = typedArray
					.getDrawable(R.styleable.FlySeekbarObj_mThumb_background);
			mMaxProgress = typedArray
					.getInt(R.styleable.FlySeekbarObj_mParts_size,
							DEFAULT_MAX_PROGRESS);

			typedArray.recycle();
		}

		mThumbPaint = new Paint();
		mThumbPaint.setStyle(Paint.Style.FILL);
		mThumbPaint.setAntiAlias(true);

		mProgressRect = new Rect();
		mSectionPointRect = new Rect();
		mThumbDestRect = new Rect();

		mCurrProgress = mMinProgress;

		mBackgroundRect = new Rect();

	}

	private void initViewSize(int width, int height) {
		if (width <= 0 || height <= 0) {
			return;
		}
		mProgressWidth = width;
		mMaxAnimatorTime = width*3;
		mThumbRadius = width / (mMaxProgress + 1) / 2;
		if (HORIZONTAL == mOrientation) {
			if (mThumbScaleEnable) {
				mProgressLength = (int) (width - 2 * mThumbRadius
						* mMaxThumbScale);
			} else {
				mProgressLength = width - 2 * mThumbRadius;
			}

			mBackgroundRect.top = -height / 2;
			mBackgroundRect.bottom = -mBackgroundRect.top;
			mBackgroundRect.left = -width / 2;
			mBackgroundRect.right = width / 2;

			mProgressRect.top = -height / 2;
			mProgressRect.bottom = -mProgressRect.top;
			mProgressRect.left = mOverspreadEnable ? -width / 2
					: -mProgressLength / 2;
			mProgressRect.right = -mProgressLength / 2;
			Log.d("mThumbDestRect", "mProgressRect " + mProgressRect.toString());

			mThumbDestRect.top = -(height * 24 / 50);
			mThumbDestRect.bottom = -mThumbDestRect.top;
			mThumbDestRect.left = -mProgressLength / 2 - mThumbRadius;
			mThumbDestRect.right = -mProgressLength / 2 + mThumbRadius;
			Log.d("mThumbDestRect",
					"mThumbDestRect2 " + mThumbDestRect.toString());
		} else {
			if (mThumbScaleEnable) {
				mProgressLength = (int) (height - 2 * mThumbRadius
						* mMaxThumbScale);
			} else {

				mProgressLength = height - 2 * mThumbRadius;
			}
			mBackgroundRect.top = mOverspreadEnable ? -height / 2
					: -mProgressLength / 2;
			mBackgroundRect.bottom = mOverspreadEnable ? height / 2
					: mProgressLength / 2;
			mBackgroundRect.left = -mProgressWidth;
			mBackgroundRect.right = mProgressWidth;

			mProgressRect.top = mProgressLength / 2;
			mProgressRect.bottom = mOverspreadEnable ? height / 2
					: mProgressLength / 2;
			mProgressRect.left = -mProgressWidth;
			mProgressRect.right = mProgressWidth;

			mThumbDestRect.top = -mProgressLength / 2 - mThumbRadius;
			mThumbDestRect.bottom = -mProgressLength / 2 + mThumbRadius;
			mThumbDestRect.left = -mThumbRadius;
			mThumbDestRect.right = mThumbRadius;

		}

		mSectionPointRect.top = -mSectionPointRadius;
		mSectionPointRect.bottom = mSectionPointRadius;
		mSectionPointRect.left = -mSectionPointRadius;
		mSectionPointRect.right = mSectionPointRadius;
		mThumbTouchOffset = mThumbRadius;
		setBackground(mBackgroundDrawable);
		setThumbDrawable(mThumbDrawable);
		setProgress(mCurrProgress);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);
		if (widthMode == MeasureSpec.EXACTLY) {
			mViewWidth = widthSize;
		} else {
			mViewWidth = getWidth();
		}
		if (heightMode == MeasureSpec.EXACTLY) {
			mViewHeight = heightSize;
		} else {
			mViewHeight = getHeight();
		}
		initViewSize(mViewWidth, mViewHeight);
		setMeasuredDimension(mViewWidth, mViewHeight);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float x = event.getX() - mViewWidth / 2;
		float y = event.getY() - mViewHeight / 2;
		ViewParent parent = getParent();

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			if (!mIsThumbEnable) {
				return super.onTouchEvent(event);
			}
			if (clickOnThumb(x, y)) {
				dealThumbAnim(true);
				mIsClickOnThumb = true;
				mIsClickOnProgress = true;
				if (null != parent) {
					parent.requestDisallowInterceptTouchEvent(true);
				}
			} else if (clickOnProgress(x, y)) {
				dealThumbAnim(true);
				mIsClickOnProgress = true;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			if (mIsClickOnThumb) {
				moveToPoint(HORIZONTAL == mOrientation ? x : y);
				onProgressChanged(false, HORIZONTAL == mOrientation ? x : y);
			}
			break;
		case MotionEvent.ACTION_UP:
			dealThumbAnim(false);
			mIsClickOnThumb = false;
			if (mIsClickOnProgress) {
				mIsClickOnProgress = false;
				onProgressChanged(true, HORIZONTAL == mOrientation ? x : y);
			}
			if (null != parent) {
				parent.requestDisallowInterceptTouchEvent(false);
			}
			break;
		default:
			break;
		}
		return true;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.save();
		canvas.translate(mViewWidth / 2, mViewHeight / 2);
		drawBackground(canvas);
		drawProgressBackground(canvas);
		drawThumb(canvas);
		drawText(canvas);
		canvas.restore();
	}

	/**
	 * Draw the progress background.
	 * 
	 * @param canvas
	 *            Canvas
	 */
	private void drawBackground(Canvas canvas) {
		canvas.save();
		mBackgroundDrawable.setBounds(mBackgroundRect);
		mBackgroundDrawable.draw(canvas);
		canvas.restore();
	}

	/**
	 * Draw the progress background.
	 * 
	 * @param canvas
	 *            Canvas
	 */
	private void drawProgressBackground(Canvas canvas) {
		canvas.save();
		mProgressBackgroundDrawable.setBounds(mBackgroundRect);
		mProgressBackgroundDrawable.draw(canvas);
		canvas.restore();
	}

	/**
	 * Draw the progress Thumb.
	 * 
	 * @param canvas
	 *            Canvas
	 */
	private void drawThumb(Canvas canvas) {
		canvas.save();
		mThumbDrawable.setBounds(mThumbDestRect);
		mThumbDrawable.draw(canvas);
		canvas.restore();
	}

	private void drawText(Canvas canvas) {
		canvas.save();
		canvas.clipRect(mThumbDestRect);
		mBackgroundDrawable.setBounds(mBackgroundRect);
		mBackgroundDrawable.draw(canvas);
		canvas.restore();
	}

	/**
	 * Whether click on thumb.
	 * 
	 * @param x
	 *            x
	 * @param y
	 *            y
	 * @return true or false
	 */
	private boolean clickOnThumb(float x, float y) {
		return mThumbDestRect.left < mThumbDestRect.right
				&& mThumbDestRect.top < mThumbDestRect.bottom
				&& x >= mThumbDestRect.left - mThumbTouchOffset
				&& x <= mThumbDestRect.right + mThumbTouchOffset
				&& y >= mThumbDestRect.top - mThumbTouchOffset
				&& y <= mThumbDestRect.bottom + mThumbTouchOffset;
	}

	/**
	 * Whether click on progress.
	 * 
	 * @param x
	 *            x
	 * @param y
	 *            y
	 * @return true or false
	 */
	private boolean clickOnProgress(float x, float y) {
		return mBackgroundRect.left < mBackgroundRect.right
				&& mBackgroundRect.top < mBackgroundRect.bottom
				&& x >= mBackgroundRect.left - mThumbTouchOffset
				&& x <= mBackgroundRect.right + mThumbTouchOffset
				&& y >= mBackgroundRect.top - mThumbTouchOffset
				&& y <= mBackgroundRect.bottom + mThumbTouchOffset;
	}

	/**
	 * Deal the listener of progress changed.
	 * 
	 * @param isFinished
	 *            whether changing is finished
	 */
	private void onProgressChanged(boolean isFinished, float f) {
		int tempProgress = coord2Progress((int) f);
		if (mLastProgress != tempProgress && !isFinished) {
			if (null != mOnProgressListener) {
				mIsFromUser = true;
				mOnProgressListener.onChanged(this, true, isFinished);
				mIsFromUser = false;
			}
			mLastProgress = mCurrProgress = tempProgress;

		}
		if (isFinished) { // adjust thumb position when finished.
			setProgress(tempProgress);
		}
		mIsMovedOut = false;
	}

	/**
	 * Move the thumb position.
	 * 
	 * @param coord
	 *            the x or y coordinate of thumb
	 */
	private void moveToPoint(float coord) {
		float halfLength = mProgressLength / 2;
		if (coord > halfLength) {
			coord = halfLength;
			mIsMovedOut = true;
			mCurrProgress = HORIZONTAL == mOrientation ? mMaxProgress
					: mMinProgress;
		} else if (coord < -halfLength) {
			coord = -halfLength;
			mIsMovedOut = true;
			mCurrProgress = HORIZONTAL == mOrientation ? mMinProgress
					: mMaxProgress;
		}

		if (HORIZONTAL == mOrientation) {
			mThumbDestRect.left = (int) (coord - mThumbRadius);
			mThumbDestRect.right = (int) (coord + mThumbRadius);
			mProgressRect.right = (int) coord;
		} else {
			mThumbDestRect.top = (int) (coord - mThumbRadius);
			mThumbDestRect.bottom = (int) (coord + mThumbRadius);
			mProgressRect.top = (int) coord;
		}
		invalidate();
	}

	/**
	 * Return x or y coordinate by progress value.
	 * 
	 * @param progress
	 *            progress value
	 * @return x or y coordinate
	 */
	private float progress2Coord(int progress) {
		if (HORIZONTAL == mOrientation) {
			return (float) mProgressLength * (progress - mMinProgress)
					/ (mMaxProgress - mMinProgress) - mProgressLength / 2f;
		} else {
			return mProgressLength / 2 - (float) mProgressLength
					* (progress - mMinProgress) / (mMaxProgress - mMinProgress);
		}
	}

	/**
	 * Return progress value by x or y coordinate.
	 * 
	 * @param coord
	 *            x or y coordinate
	 * @return progress value
	 */
	private int coord2Progress(int coord) {
		if (coord > mProgressLength / 2) {
			return HORIZONTAL == mOrientation ? mMaxProgress : mMinProgress;
		} else if (coord < -mProgressLength / 2) {
			return HORIZONTAL == mOrientation ? mMinProgress : mMaxProgress;
		} else {
			if (HORIZONTAL == mOrientation) {
				return Math.round((coord + mProgressLength / 2f)
						* (mMaxProgress - mMinProgress) / mProgressLength)
						+ mMinProgress;
			} else {
				return Math.round((mProgressLength / 2f - coord)
						* (mMaxProgress - mMinProgress) / mProgressLength)
						+ mMinProgress;
			}
		}
	}

	/**
	 * Change thumb scale.
	 * 
	 * @param enlarge
	 *            enlarge
	 */
	private void changeThumbScale(boolean enlarge) {
		if (Build.VERSION.SDK_INT >= 12) {
			float start = mThumbScale;
			float stop = enlarge ? mMaxThumbScale : 1f;
			if (null == mValueAnimator) {
				mValueAnimator = new ValueAnimator();
				mValueAnimator.setDuration(200);
				mValueAnimator.setInterpolator(new DecelerateInterpolator());
				mValueAnimator
						.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

							@Override
							public void onAnimationUpdate(
									ValueAnimator animation) {
								mThumbScale = (Float) animation
										.getAnimatedValue();
								invalidate();
							}
						});
			} else {
				mValueAnimator.cancel();
			}
			mValueAnimator.setFloatValues(start, stop);
			mValueAnimator.start();
		} else {
			mThumbScale = enlarge ? mMaxThumbScale : 1f;
		}
	}

	/**
	 * Deal the thumb scale.
	 * 
	 * @param enlarge
	 *            enlarge
	 */
	private void dealThumbAnim(boolean enlarge) {
		if (!mThumbScaleEnable) {
			return;
		}
		if (enlarge) {
			removeCallbacks(thumbAnimRunnable);
			changeThumbScale(true);
		} else {
			removeCallbacks(thumbAnimRunnable);
			postDelayed(thumbAnimRunnable, THUMB_ANIM_CANCEL_SPACE);
		}
	}

	/**
	 * The runnable for thumb anim.
	 */
	private Runnable thumbAnimRunnable = new Runnable() {
		@Override
		public void run() {
			changeThumbScale(false);
		}
	};

	/**
	 * Change thumb to dest progress.
	 * 
	 * @param anim
	 *            true show anim, false not show anim
	 * @param progress
	 *            progress
	 */
	private void toDestProgress(boolean anim, int progress) {
		if (anim && Build.VERSION.SDK_INT >= 12) {
			float startCoord = checkCoord(progress2Coord(mCurrProgress));
			float stopCoord = checkCoord(progress2Coord(progress));
			int move = mCurrProgress - progress;
			move = move > 0?move:-move;
			mCurrProgress = progress;
			if (null == mToDestValueAnimator) {
				mToDestValueAnimator = new ValueAnimator();

				mToDestValueAnimator
						.setInterpolator(new DecelerateInterpolator());
				mToDestValueAnimator
						.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
							@Override
							public void onAnimationUpdate(
									ValueAnimator animation) {
								float coord = (Float) animation
										.getAnimatedValue();
								moveToPoint(coord);
							}
						});
			} else {

				mToDestValueAnimator.cancel();
			}
			mToDestValueAnimator.setDuration(mMaxAnimatorTime*move/mMaxProgress);
			mToDestValueAnimator.setFloatValues(startCoord, stopCoord);
			mToDestValueAnimator.start();
		} else {
			mCurrProgress = progress;
			float stopCoord = checkCoord(progress2Coord(progress));
			moveToPoint(stopCoord);
		}
	}

	/**
	 * Check the coordinate
	 * 
	 * @param coord
	 *            coordinate
	 * @return the new coordinate
	 */
	private float checkCoord(float coord) {
		float halfLength = mProgressLength / 2;
		if (coord > halfLength) {
			return halfLength;
		} else if (coord < -halfLength) {
			return -halfLength;
		}
		return coord;
	}

	/**
	 * Whether changed by user.
	 * 
	 * @return true by user, false not by user
	 */
	public boolean isFromUser() {
		return mIsFromUser;
	}

	/**
	 * Whether change finished.
	 * 
	 * @return true finished, false not finish
	 */
	public boolean isFinished() {
		return !mIsClickOnProgress && !mIsClickOnThumb;
	}

	/**
	 * Get last progress.
	 * 
	 * @return last progress
	 */
	public int getLastProgress() {
		return mLastProgress;
	}

	/**
	 * Get current progress.
	 * 
	 * @return the current progress
	 */
	public int getProgress() {
		return mCurrProgress;
	}

	/**
	 * Set progress.
	 * 
	 * @param progress
	 *            progress
	 * @param anim
	 *            true show anim, false not show anim
	 * @param fromUser
	 *            true by user, false not by user
	 */
	public void setProgress(int progress, boolean anim, boolean fromUser) {
		if (!mIsThumbEnable || !isFinished()) {
			return; // can not be changed when touching
		}
		if (progress <= mMinProgress) {
			progress = mMinProgress;
		} else if (progress >= mMaxProgress) {
			progress = mMaxProgress;
		}

		toDestProgress(anim, progress);
		if (null != mOnProgressListener) {
			if (mLastProgress != mCurrProgress) {
				mIsFromUser = fromUser;
				mOnProgressListener.onChanged(this, mIsFromUser, true);
				mIsFromUser = false;
			}
			mLastProgress = mCurrProgress;
		}

	}

	public void setProgress(int progress) {
		setProgress(progress, true, false);
	}

	public void setMinProgress(int minProgress) {
		this.mMinProgress = minProgress;
		if (mCurrProgress < mMinProgress) {
			this.mCurrProgress = mMinProgress;
		}
	}
	
	/**
	 * Set the parts
	 * @param maxProgress
	 */
	public void setMaxProgress(int maxProgress) {
		this.mMaxProgress = maxProgress;
	}

	/**
	 * Set the Background drawable.
	 * 
	 * @param drawable
	 *            Drawable
	 */
	public void setBackground(Drawable drawable) {
		if (null == drawable) {
			return;
		}
		mBackgroundDrawable = drawable;
	}

	/**
	 * Set the Progress drawable.
	 * 
	 * @param drawable
	 *            Drawable
	 */
	public void setProgressBackground(Drawable drawable) {
		if (null == drawable) {
			return;
		}
		mProgressBackgroundDrawable = drawable;
	}

	/**
	 * Set the thumb drawable.
	 * 
	 * @param drawable
	 *            Drawable
	 */
	public void setThumbDrawable(Drawable drawable) {
		if (null == drawable) {
			return;
		}
		mThumbDrawable = drawable;
	}

	/**
	 * Register a callback to be invoked when the progress changes.
	 * 
	 * @param listener
	 *            the callback to call on progress change
	 */
	public void setOnProgressChangedListener(OnProgressChangedListener listener) {
		mOnProgressListener = listener;
	}

	public interface OnProgressChangedListener {
		void onChanged(FlySeekBar seekBar, boolean fromUser, boolean isFinished);
	}

}
